package org.fglxy.mockserver.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.fglxy.mockserver.entity.MockEndEntity;
import org.fglxy.mockserver.entity.MockTaskEntity;
import org.fglxy.mockserver.enums.RetCodeEnum;
import org.fglxy.mockserver.exception.MessageException;
import org.springframework.web.multipart.MultipartFile;

public class ResourceUtils {
	// 获取项目根路径
	private static String path = System.getProperty("index.resources.home");
	// mockId级别的读写锁，确保配置的数据完整性.mock任务不会很多，所以这里不考虑清除无用的锁.
	private static ConcurrentHashMap<String, ReadWriteLock> READ_WRITE_LOCK_MAP = new ConcurrentHashMap<String, ReadWriteLock>();

	public static void readLock(String mockId) {
		ReadWriteLock lock = getLock(mockId);
		lock.readLock().lock();
	}

	private static ReadWriteLock getLock(String mockId) {
		ReadWriteLock newLock = new ReentrantReadWriteLock();
		ReadWriteLock lock = READ_WRITE_LOCK_MAP.putIfAbsent(mockId, newLock);
		lock = lock == null ? newLock : lock;
		return lock;
	}

	public static void releaseReadLock(String mockId) {
		ReadWriteLock lock = getLock(mockId);
		lock.readLock().unlock();
	}

	public static void writeLock(String mockId) {
		ReadWriteLock lock = getLock(mockId);
		lock.writeLock().lock();
	}

	public static void releaseWriteLock(String mockId) {
		ReadWriteLock lock = getLock(mockId);
		lock.writeLock().unlock();
	}

	public static List<MockTaskEntity> getTasksByMockId(String mockId) throws IOException {
		String rootPath = path + mockId;
		List<MockTaskEntity> tasks = new ArrayList<>();
		Files.walkFileTree(Paths.get(rootPath), new SimpleFileVisitor<Path>() {
			@Override
			public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
				String name = file.toFile().getName();
				if (name.endsWith(".js")) {
					MockTaskEntity task = new MockTaskEntity();
					name = name.substring(0, name.length() - 3);
					task.setName(name);
					String script = new String(Files.readAllBytes(file), Charset.defaultCharset());
					task.setScript(script);
					tasks.add(task);
				}
				return FileVisitResult.CONTINUE;
			}
		});
		return tasks;
	}

	/**
	 * 描述文件必须存在，且必须是proto.desc这个名字。否则直接空指针
	 * @param mockId
	 * @return
	 * @throws IOException 
	 */
	public static InputStream getDescriptorStreamByMockId(String mockId) throws IOException {
		String descriptPath = path + mockId + "/proto.desc";
		if (!Files.exists(Paths.get(descriptPath))) {
			return null;
		}
		return Files.newInputStream(Paths.get(descriptPath));
	}

	public static List<String> getCmdNoMapByMockId(String mockId) throws IOException {
		String cmdNos = path + mockId + "/cmdno.properties";
		if (!Files.exists(Paths.get(cmdNos))) {
			return null;
		}
		return Files.readAllLines(Paths.get(cmdNos));
	}
	
	public static MockEndEntity getMockBaseConfig(String mockId) throws IOException {
		String configPath = path + mockId + "/config.json";
		if (!Files.exists(Paths.get(configPath))) {
			return null;
		}
		String json = new String(Files.readAllBytes(Paths.get(configPath)), Charset.defaultCharset());
		return JSONUtils.parse(json, MockEndEntity.class);
	}

	public static void writeMockBaseConfig(MockEndEntity entity) {
		String json = JSONUtils.toJson(entity);
		File file = new File(path + entity.getId() + "/config.json");
		if (!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		if (file.exists()) {
			file.delete();
		}
		try {
			Files.write(file.toPath(), json.getBytes());
		} catch (IOException e) {
			throw new MessageException(RetCodeEnum.WRITE_MOCK_BASE_CONFIG_FAIL, e);
		}
	}

	public static void writeMockDescript(String id, MultipartFile file) {
		Path descriptPath = Paths.get(path + id + "/proto.desc");
		if (descriptPath.toFile().exists()) {
			descriptPath.toFile().delete();
		}
		try (
				OutputStream output = Files.newOutputStream(descriptPath);
				) {
			IOUtils.copy(file.getInputStream(), output);
		} catch (IOException e) {
			throw new MessageException(RetCodeEnum.WRITE_MOCK_DESCRIPT_FAIL, e);
		}
	}

	public static void deleteMock(String mockId) {
		File file = new File(path + mockId);
		if (file.exists()) {
			file.delete();
		}
	}

	public static void writeMockTask(String mockId, MockTaskEntity entity) {
		File file = new File(path + mockId + "/" + entity.getName() + ".js");
		if (file.exists()) {
			file.delete();
		}
		try {
			Files.write(file.toPath(), entity.getScript().getBytes());
		} catch (IOException e) {
			throw new MessageException(RetCodeEnum.WRITE_MOCK_TASK_FAIL, e);
		}
	}

	public static void deleteMockTask(String mockId, String taskName) {
		File file = new File(path + mockId + "/" + taskName + ".js");
		if (file.exists()) {
			file.delete();
		}
	}

	public static void writeMockCmdNos(String id, MultipartFile cmdNos) {
		String cmdNosPath = path + id + "/cmdno.properties";
		File file = new File(cmdNosPath);
		if (file.exists()) {
			file.delete();
		}
		try (
				OutputStream output = Files.newOutputStream(file.toPath());
				) {
			IOUtils.copy(cmdNos.getInputStream(), output);
		} catch (IOException e) {
			throw new MessageException(RetCodeEnum.WRITE_MOCK_CMDNOS_FAIL, e);
		}
	}

	public static List<MockEndEntity> allMock() throws IOException {
		String rootPath = path;
		List<MockEndEntity> mocks = new ArrayList<>();
		Files.walkFileTree(Paths.get(rootPath), new SimpleFileVisitor<Path>() {
			@Override
			public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
				if (file.toFile().getName().equals("config.json")) {
					String json = new String(Files.readAllBytes(file), Charset.defaultCharset());
					MockEndEntity mock = JSONUtils.parse(json, MockEndEntity.class);
					mocks.add(mock);
				}
				return FileVisitResult.CONTINUE;
			}
		});
		return mocks;
	}

	public static void initResourcePath() {
		File file = new File(path);
		if (!file.exists()) {
			file.mkdirs();
		}
	}

}
